package org.lmind.rom.util;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

import org.apache.commons.collections.iterators.UnmodifiableIterator;
import org.apache.commons.io.input.SwappedDataInputStream;

public class ZipEntryViewer {
	
	public static final int END_OF_CENTRAL_DIRECTORY_SIZE = 22;
	
	private HashMap<String, ZipEntry> files;
	
	public ZipEntryViewer(RemoteFile file) throws IOException {
		view(file);
	}
	
	@SuppressWarnings("unchecked")
	public Iterator<ZipEntry> entries() {
		return UnmodifiableIterator.decorate(files.values().iterator());
	}
	
	public ZipEntry getEntry(String name) {
		return files.get(name);
	}
	
	private void view(RemoteFile file) throws IOException {
		
		InputStream data = null;
		InputStream centralDirectoryData = null;
		
		try {
			long bufSize = 1024;
			byte[] buf = new byte[(int) Math.min(file.getSize(), bufSize)];
			data = file.openRead(file.getSize() - buf.length, buf.length);
			new DataInputStream(data).readFully(buf);
			
			EndOfCentralDirectory end = findCentralDirectory(buf);
			centralDirectoryData = file.openRead(end.getOffsetOfCentralDirectory(), file.getSize() - end.getOffsetOfCentralDirectory());
			
			files = new HashMap<String, ZipEntry>();
			for (int i = 0; i < end.getEntriesCount(); i++) {
				CentralFileHeader header = CentralFileHeader.read(centralDirectoryData);
				ZipEntry entry = new ZipEntry();
				entry.setName(header.getFileName());
				entry.setOffset(header.getRelativeOffsetOfLocalHeader());
				entry.setDataOffset(entry.getOffset() + header.getNameLength() + header.getExtraFieldLength());
				entry.setCompressedSize(header.getCompressedSize());
				entry.setUncompressedSize(header.getUncompressedSize());
				files.put(entry.getName(), entry);
				System.out.println(entry.getName());
			}

		} finally {
			if (data != null)
				data.close();
			if (centralDirectoryData != null)
				centralDirectoryData.close();
		}
	}
	
	private EndOfCentralDirectory findCentralDirectory(byte[] data) throws IOException {
		
		int sig = 0x06054b50;
		
		for (int i = data.length - END_OF_CENTRAL_DIRECTORY_SIZE; i >= 0; i--) {
			SwappedDataInputStream input = new SwappedDataInputStream(new ByteArrayInputStream(data, i, data.length - i));
			int s = input.readInt();
			if (sig != s)
				continue;
			input.skip(6);
			int entriesCount = input.readUnsignedShort();
			input.skip(4);
			int offset = input.readInt();
			int commentLength = input.readUnsignedShort();
			if (input.available() != commentLength)
				continue;
			
			EndOfCentralDirectory end = new EndOfCentralDirectory();
			end.setEntriesCount(entriesCount);
			end.setOffsetOfCentralDirectory(offset);
			return end;
		}
		throw new IOException();
	}
	
	public static void main(String[] args) {
		try {
			HttpRemoteFile file = new HttpRemoteFile(new URL("http://127.0.0.1:18080/nginx-1.0.12.zip"));
			ZipEntryViewer extractor = new ZipEntryViewer(file);
			ZipEntry entry = extractor.getEntry("nginx-1.0.12/conf/nginx.conf");
			
			InputStream rawData = file.openRead(entry.getOffset(), entry.getCompressedSize());
			LocalFileHeader header = LocalFileHeader.read(rawData);
			InflaterInputStream input = new InflaterInputStream(rawData, new Inflater(true));
			
			byte[] buf = new byte[(int)entry.getUncompressedSize()];
			input.read(buf);
			
			String str = new String(buf, "ascii");
			System.out.println(str);
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// TODO: handle finally
		}
	}
}
